diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 1b737c7ec7..5ed9b6100c 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -4,6 +4,18 @@ Web Console preserves compatible API paths, schema and resource names, action na Visible branding, product-owned assets, icon identifiers, package metadata, and operator documentation use PastureStack. Historical identifiers remain only where they are server data or protocol contracts and must not be mechanically replaced. +Candidate `1.6.178` handles only the exact `inactive` environment state specially, +after global project and member authorization succeeds. Network and +policy-manager values remain unavailable (`null`), with a state-specific +localized explanation; they are not invented empty resource collections. +Project metadata, membership and removal capabilities remain sourced from the +API. A stale editable network cannot be persisted from that inactive form. +All other states retain the existing scoped reads and error semantics. Backend +authorization, session generation/mutex, MFA, OIDC, workspace lifecycle and +dependencies are unchanged. Formal publication and deployed native acceptance +are pending; historical HOLDs and the incomplete full matrix remain unchanged. +See the [candidate release note](docs/releases/web-console-1.6.178.md). + Published `1.6.177` treats an ended workspace entry as terminal across both logs and terminal components. Late responses and queued socket/timer callbacks are bound to the original entry, never an explicitly opened replacement. Existing diff --git a/README.md b/README.md index de64d5c5d9..251c99fc88 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,19 @@ PastureStack is an independent community effort to preserve, audit, and moderniz ## Project status +Candidate `1.6.178` fixes viewing and editing an inactive environment. Once the +globally authorized project and members have loaded, the page does not request +network or policy-manager data scoped to that inactive environment: the API +correctly denies those requests. The existing form explains that network +settings are unavailable until activation, in all thirteen packaged locales. +Metadata and membership controls still follow their original API action links; +cached network data cannot enable a network save. Other states and authorization +failures retain the established error handling. This candidate changes no +backend, authentication, session ownership or dependency graph. Formal +publication and deployed native acceptance remain pending; it does not promote +earlier HOLD results or claim completion of the full matrix. See the +[candidate release note](docs/releases/web-console-1.6.178.md). + Published `1.6.177` prevents ended log and terminal workspace entries from reconnecting after remount/reload or from late access-ticket, broker, socket and timer callbacks. Both components share one lifecycle boundary; asynchronous diff --git a/app/components/view-edit-project/component.js b/app/components/view-edit-project/component.js index 07f50a298a..ed7d3ff9fc 100644 --- a/app/components/view-edit-project/component.js +++ b/app/components/view-edit-project/component.js @@ -20,6 +20,7 @@ export default Component.extend(NewOrEdit, Sortable, { originalProject: null, allProjects: null, policyManager: null, + networkUnavailableForInactiveProject: false, editing: false, tab: 'access', @@ -150,8 +151,8 @@ export default Component.extend(NewOrEdit, Sortable, { }.property('project.id', 'project.actionLinks.setmembers'), canEditNetwork: function() { - return !!this.get('network.actionLinks.update') && !this.get('missingManager') && !this.get('hasUnsupportedPolicy'); - }.property('network.actionLinks.update', 'missingManager', 'hasUnsupportedPolicy'), + return !this.get('networkUnavailableForInactiveProject') && !!this.get('network.actionLinks.update') && !this.get('missingManager') && !this.get('hasUnsupportedPolicy'); + }.property('networkUnavailableForInactiveProject', 'network.actionLinks.update', 'missingManager', 'hasUnsupportedPolicy'), canSave: function() { return this.get('canEditProject') || diff --git a/app/components/view-edit-project/template.hbs b/app/components/view-edit-project/template.hbs index 8a3548107a..4c6c636167 100644 --- a/app/components/view-edit-project/template.hbs +++ b/app/components/view-edit-project/template.hbs @@ -137,7 +137,11 @@ -{{#if this.network}} +{{#if this.networkUnavailableForInactiveProject}} +

+ {{t 'viewEditProject.networkPolicy.inactive'}} +

+{{else if this.network}}

{{t 'viewEditProject.networkPolicy.label'}}


diff --git a/app/settings/projects/detail/route.js b/app/settings/projects/detail/route.js index 332728df15..60a4a8f273 100644 --- a/app/settings/projects/detail/route.js +++ b/app/settings/projects/detail/route.js @@ -50,7 +50,12 @@ export default Route.extend(PromiseToCb, { } ); })], - networks: ['project', this.toCb(() => { + networks: ['importMembers', this.toCb((results) => { + // Inactive environments remain globally visible to their owners, + // but the API correctly rejects requests scoped to them. + if ( results.project.get('state') === 'inactive' ) { + return null; + } return userStore.find('network', null, { filter: {accountId: params.project_id}, headers: {[C.HEADER.PROJECT_ID]: params.project_id}, @@ -58,7 +63,10 @@ export default Route.extend(PromiseToCb, { throw this.environmentLoadError(err, 'viewEditProject.error.relatedUnavailable'); }); })], - policyManagers: ['project', this.toCb(() => { + policyManagers: ['importMembers', this.toCb((results) => { + if ( results.project.get('state') === 'inactive' ) { + return null; + } return userStore.find('stack', null, policyManagerOpt).then(null, (err) => { throw this.environmentLoadError(err, 'viewEditProject.error.relatedUnavailable'); }); @@ -75,7 +83,7 @@ export default Route.extend(PromiseToCb, { }, 'Load all the things'); return promise.then((hash) => { - let network = hash.networks.find((x) => C.PROJECT.SUPPORTS_NETWORK_POLICY.includes(x.get('name'))); + let network = hash.networks ? hash.networks.find((x) => C.PROJECT.SUPPORTS_NETWORK_POLICY.includes(x.get('name'))) : null; if ( network ) { network = network.clone(); @@ -106,7 +114,8 @@ export default Route.extend(PromiseToCb, { let out = EmberObject.create({ all: hash.allProjects, network: network, - policyManager: hash.policyManagers.objectAt(0), + policyManager: hash.policyManagers ? hash.policyManagers.objectAt(0) : null, + networkUnavailableForInactiveProject: hash.project.get('state') === 'inactive', }); if ( params.editing ) { diff --git a/app/settings/projects/detail/template.hbs b/app/settings/projects/detail/template.hbs index d2cb7f2848..4d63521636 100644 --- a/app/settings/projects/detail/template.hbs +++ b/app/settings/projects/detail/template.hbs @@ -6,6 +6,7 @@ initialStacks=this.model.stacks serviceChoices=this.model.serviceChoices policyManager=this.model.policyManager + networkUnavailableForInactiveProject=this.model.networkUnavailableForInactiveProject showEdit=this.editing editing=true tab=this.tab diff --git a/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json b/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json index 82eb97fd86..5c2278190e 100644 --- a/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json +++ b/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json @@ -1,12 +1,12 @@ { "name": "@pasturestack/web-console", - "version": "1.6.177", + "version": "1.6.178", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@pasturestack/web-console", - "version": "1.6.177", + "version": "1.6.178", "license": "Apache-2.0", "dependencies": { "sass": "1.103.1" diff --git a/docs/releases/web-console-1.6.178.md b/docs/releases/web-console-1.6.178.md new file mode 100644 index 0000000000..4d7fffbee9 --- /dev/null +++ b/docs/releases/web-console-1.6.178.md @@ -0,0 +1,37 @@ +# Web Console 1.6.178 — inactive environment details + +Status: source candidate; formal publication and deployed native acceptance +are pending. No earlier HOLD is promoted to PASS. + +## Cause and correction + +An inactive environment remains globally visible to an authorized owner, but +the engine rejects API requests scoped to that inactive environment. The +details route unconditionally requested networks and policy-manager stacks; +their 403 responses prevented its model from committing and obscured otherwise +authorized metadata and membership information. + +The route now waits for globally authorized project and membership reads, +then skips only those two inapplicable scoped reads when the project state is +exactly `inactive`. It returns `null` for the unavailable data and a dedicated +reason flag. The existing details component displays a translated explanation +in all thirteen packaged locales and prevents a cached network from enabling network saves. +Metadata, member and removal action links are unchanged. Active and transitional +states, denied global data, expired sessions and other failures keep the +existing authorization and error behavior. + +## Change boundary and verification + +Changes are confined to the details route/template, shared details component, +thirteen translations and their three focused test modules. Release-version metadata +and the matching reviewed lock baseline move together; dependencies do not +change. No API, engine, provider, HAProxy, authentication or session contract is +modified. The existing ended log/terminal and cross-tab session tests are +retained. + +Added regressions cover inactive view/edit, globally denied project/member +reads, non-inactive scoped denial, the actual details-template reason flag and +stale-network write prevention. Formal exact-source tests, reproducible static +archives, immutable publication and isolated native view/edit/reload/removal +acceptance must be recorded separately. Full role/resource/locale acceptance +remains incomplete. diff --git a/package-lock.json b/package-lock.json index 82eb97fd86..5c2278190e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@pasturestack/web-console", - "version": "1.6.177", + "version": "1.6.178", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@pasturestack/web-console", - "version": "1.6.177", + "version": "1.6.178", "license": "Apache-2.0", "dependencies": { "sass": "1.103.1" diff --git a/package.json b/package.json index fa0af56534..648adb0516 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@pasturestack/web-console", - "version": "1.6.177", + "version": "1.6.178", "private": true, "description": "PastureStack browser console for the compatible control platform.", "repository": { diff --git a/scripts/check-modernization-blockers b/scripts/check-modernization-blockers index c7e84ecc97..69984e687a 100755 --- a/scripts/check-modernization-blockers +++ b/scripts/check-modernization-blockers @@ -41,8 +41,8 @@ with open('package.json', encoding='utf-8') as f: print(json.load(f).get('version', '')) PY ) -if [[ "$version" != "1.6.177" ]]; then - echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.177" +if [[ "$version" != "1.6.178" ]]; then + echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.178" failures=$((failures + 1)) fi diff --git a/scripts/check-ui-console-workspace b/scripts/check-ui-console-workspace index 8aca861389..c5eb40c6e2 100755 --- a/scripts/check-ui-console-workspace +++ b/scripts/check-ui-console-workspace @@ -143,4 +143,4 @@ if [[ -n ${PASTURESTACK_PRIVATE_MARKER:-} ]] && grep -RInF -- "$PASTURESTACK_PRI fi printf 'UI_CONSOLE_WORKSPACE_OK version=%s persistence=%s cross_tab=%s\n' \ - 1.6.177 browser-session broker-broadcast + 1.6.178 browser-session broker-broadcast diff --git a/scripts/check-ui-critical-high-dependencies b/scripts/check-ui-critical-high-dependencies index 2d6af6ee44..3caafd7317 100755 --- a/scripts/check-ui-critical-high-dependencies +++ b/scripts/check-ui-critical-high-dependencies @@ -70,7 +70,7 @@ if lock_bytes != baseline_bytes: lock = json.loads(lock_bytes) packages = lock.get("packages", {}) root = packages.get("", {}) -if package.get("version") != "1.6.177": +if package.get("version") != "1.6.178": fail(f"unexpected Web Console version: {package.get('version')}") if root.get("version") != package.get("version"): fail(f"lock root version differs: {root.get('version')}") diff --git a/tests/integration/components/view-edit-project-permissions-test.js b/tests/integration/components/view-edit-project-permissions-test.js index e1d4ac0839..ecdcfa5d9a 100644 --- a/tests/integration/components/view-edit-project-permissions-test.js +++ b/tests/integration/components/view-edit-project-permissions-test.js @@ -1,14 +1,17 @@ import { A } from '@ember/array'; import Component from '@ember/component'; +import EmberRouter from '@ember/routing/router'; import EmberObject, { get } from '@ember/object'; -import Service from '@ember/service'; +import Service, { service } from '@ember/service'; import { precompileTemplate } from '@ember/template-compilation'; import { click, find, findAll, render, select, settled, setupContext, setupRenderingContext, teardownContext } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { initialize as initializePodLayouts } from 'ui/initializers/pod-component-layouts'; +import ViewEditProject from 'ui/components/view-edit-project/component'; import ProjectTemplate from 'ui/models/projecttemplate'; import Router from 'ui/router'; +import ProjectDetailTemplate from 'ui/settings/projects/detail/template'; import { destroyOwned } from '../../helpers/owned-subject'; import resolver from '../../helpers/resolver'; @@ -218,6 +221,63 @@ module('Integration | Component | view edit project permissions', function(hooks assert.strictEqual(findAll('.footer-actions button').length, 2); }); + for (let editing of [false, true]) { + test(`inactive detail ${editing ? 'edit' : 'view'} shows its state reason without scoped controls`, async function(assert) { + // Use an isolated route map; the application's extension registry is + // intentionally consumed only once by its native Router. + let DetailRouter = EmberRouter.extend({location: 'none'}); + DetailRouter.map(function() { + this.route('settings', function() { + this.route('projects', {path: '/env'}, function() { + this.route('detail', {path: '/:project_id'}); + }); + }); + }); + this.owner.register('router:main', DetailRouter); + this.owner.lookup('router:main').setupRouter(); + this.owner.register('component:action-menu', Component.extend({ + layout: precompileTemplate('{{this.model.actionLinks.remove}}'), + })); + this.owner.register('component:header-state', Component.extend({ + layout: precompileTemplate('{{this.model.state}}'), + })); + this.owner.register('component:power-select', Component.extend({ + layout: precompileTemplate(''), + })); + let actionLinks = {update: '/projects/1a21', setmembers: '/projects/1a21?action=setmembers', remove: '/projects/1a21'}; + this.project.setProperties({state: 'inactive', actionLinks}); + this.network.set('actionLinks', {update: '/networks/1n1'}); + this.model = EmberObject.create({ + project: this.project, originalProject: this.originalProject, all: A([this.project]), + network: this.network, policyManager: this.policyManager, networkUnavailableForInactiveProject: true, + }); + this.editing = editing; + this.done = () => {}; + this.cancel = () => {}; + this.owner.register('service:user-store', Service.extend(this.userStore)); + // Rendering owners do not run the application's store initializer. + // Supply its real service injection without replacing component logic. + this.owner.register('component:view-edit-project', ViewEditProject.extend({userStore: service('user-store')})); + // Render the actual detail template so its reason flag forwarding is tested. + await render(ProjectDetailTemplate); + + assert.ok(find('[data-test-inactive-network-notice]').textContent.includes('viewEditProject.networkPolicy.inactive'), 'a state-specific translated explanation is visible'); + assert.ok(find('[data-test-member-name]'), 'global membership data stays visible'); + assert.strictEqual(findAll('.radio input').length, 0, 'cached network data cannot expose policy controls'); + assert.notOk(find('[data-test-network-only-edit]'), 'no scoped network edit link is introduced'); + assert.strictEqual(this.project.get('actionLinks'), actionLinks, 'existing global capabilities are untouched'); + if ( editing ) { + assert.false(find('input[type="text"]').disabled, 'global metadata capability still enables its fields'); + assert.ok(find('[data-test-member-add]'), 'global member capability remains available'); + assert.strictEqual(findAll('table.grid select').length, 1, 'member roles remain editable'); + assert.strictEqual(findAll('.footer-actions button').length, 2, 'global save and cancel remain available'); + } else { + assert.strictEqual(find('[data-test-header-state]').textContent.trim(), 'inactive', 'the environment state remains visible'); + assert.strictEqual(find('[data-test-action-menu]').textContent.trim(), actionLinks.remove, 'the existing action menu receives the original global remove link'); + } + }); + } + test('network-only environment can reach its edit form from the detail header', async function(assert) { this.owner.register('router:main', Router); this.owner.register('component:action-menu', Component.extend({ diff --git a/tests/unit/components/view-edit-project-permissions-test.js b/tests/unit/components/view-edit-project-permissions-test.js index ce77e9afab..e4a2aee261 100644 --- a/tests/unit/components/view-edit-project-permissions-test.js +++ b/tests/unit/components/view-edit-project-permissions-test.js @@ -163,6 +163,37 @@ test('member actions cannot mutate a readonly membership list', function(assert) destroyOwned(component); }); +test('inactive network unavailability preserves global capabilities and prevents stale network saves', async function(assert) { + for (let canEdit of [false, true]) { + let writes = {project: 0, members: 0, network: 0}; + let actionLinks = canEdit ? {update: '/projects/1a21', setmembers: '/projects/1a21?action=setmembers', remove: '/projects/1a21'} : {}; + let project = EmberObject.create({ + id: '1a21', state: 'inactive', actionLinks, + projectMembers: A([EmberObject.create({externalIdType: 'oidc_user', externalId: 'owner-1', role: 'owner'})]), + validationErrors() { return A([]); }, + save() { writes.project++; return resolve(this); }, + doAction(action) { assert.strictEqual(action, 'setmembers'); writes.members++; return resolve(); }, + }); + let network = EmberObject.create({ + actionLinks: {update: '/networks/1n1'}, policy: A([]), + save() { writes.network++; return resolve(this); }, + }); + let component = makeComponent(project, network, {networkUnavailableForInactiveProject: true}); + + try { + assert.strictEqual(component.get('canEditProject'), canEdit, 'metadata still follows the global project link'); + assert.strictEqual(component.get('canEditMembers'), canEdit, 'members still follow the global setmembers link'); + assert.false(component.get('canEditNetwork'), 'even a stale editable network cannot enable scoped persistence'); + assert.strictEqual(component.get('canSave'), canEdit, 'the notice does not grant a save capability'); + await component.get('actions').save.call(component); + assert.deepEqual(writes, {project: Number(canEdit), members: Number(canEdit), network: 0}, 'only the advertised global operations are saved'); + assert.strictEqual(project.get('actionLinks'), actionLinks, 'remove and other action links are not rewritten'); + } finally { + destroyOwned(component); + } + } +}); + test('member validation errors use translated messages', function(assert) { let owner = EmberObject.create({externalIdType: 'oidc_user', externalId: 'owner-1', role: 'owner'}); let project = EmberObject.create({ diff --git a/tests/unit/settings/projects/detail/route-test.js b/tests/unit/settings/projects/detail/route-test.js index 133b943302..fed8132abb 100644 --- a/tests/unit/settings/projects/detail/route-test.js +++ b/tests/unit/settings/projects/detail/route-test.js @@ -13,6 +13,7 @@ function fixture(failureAt, failure = new Error(`${failureAt} failed`)) { let project = EmberObject.create({ id: '1a21', name: 'QA project', + state: 'active', projectMembers: null, followLink(name) { if ( failureAt === 'membersSync' ) { @@ -30,6 +31,8 @@ function fixture(failureAt, failure = new Error(`${failureAt} failed`)) { return EmberObject.create({ id: this.get('id'), name: this.get('name'), + state: this.get('state'), + actionLinks: this.get('actionLinks'), projectMembers: this.get('projectMembers'), }); }, @@ -66,6 +69,83 @@ function fixture(failureAt, failure = new Error(`${failureAt} failed`)) { return {failure, members, policyManager, project, store, relatedCalls}; } +for (let editing of [false, true]) { + test(`inactive environment ${editing ? 'edit' : 'view'} preserves global data without scoped resource reads`, async function(assert) { + let data = fixture(); + let actionLinks = {update: '/projects/1a21', setmembers: '/projects/1a21?action=setmembers', remove: '/projects/1a21'}; + data.project.setProperties({state: 'inactive', actionLinks}); + let route = ProjectDetailRoute.create({userStore: data.store}); + + try { + let model = await route.model({project_id: '1a21', editing}); + assert.deepEqual(data.relatedCalls, [], 'neither network nor policy-manager API is requested'); + assert.strictEqual(model.get('network'), null, 'network data is unavailable, not an empty collection'); + assert.strictEqual(model.get('policyManager'), null, 'no policy manager is invented'); + assert.true(model.get('networkUnavailableForInactiveProject'), 'the model exposes the exact state reason'); + assert.strictEqual(model.get('all').objectAt(0), data.project, 'the global project list is retained'); + assert.strictEqual(model.get('project.projectMembers'), data.members, 'authorized global memberships are retained'); + assert.strictEqual(model.get('project.actionLinks'), actionLinks, 'the API remains the source of metadata, member, and remove capabilities'); + assert.strictEqual(model.get('originalProject'), editing ? data.project : null); + assert.strictEqual(model.get('project') === data.project, !editing, 'only the edit form clones the project'); + } finally { + run(() => route.destroy()); + } + }); +} + +test('inactive global project and member failures remain errors before any scoped reads', async function(assert) { + let intl = EmberObject.create({t(key) { return key; }}); + for (let task of ['allProjects', 'project', 'members']) { + for (let status of [401, 403, 404, 503]) { + let failure = {status, message: 'Raw API message'}; + let data = fixture(task, failure); + data.project.set('state', 'inactive'); + let route = ProjectDetailRoute.create({userStore: data.store, intl}); + + try { + await route.model({project_id: '1a21', editing: false}).then( + () => assert.ok(false, `${task} ${status} must reject`), + (error) => { + assert.strictEqual(error.status, status === 403 || status === 404 ? 404 : status, + `${task} ${status} keeps the established error classification`); + if ( status === 401 ) { + assert.strictEqual(error, failure, 'expired-session handling is unchanged'); + } + } + ); + assert.deepEqual(data.relatedCalls, [], `${task} ${status} cannot start scoped reads`); + } finally { + run(() => route.destroy()); + } + } + } +}); + +test('only exact inactive skips scoped reads; other states still reject related 403s', async function(assert) { + let intl = EmberObject.create({t(key) { return key; }}); + for (let state of ['active', 'deactivating', 'activating', 'removed', 'upgrading', 'updating-active', 'unknown', null, undefined]) { + for (let task of ['networks', 'policyManagers']) { + let data = fixture(task, {status: 403, message: 'Forbidden'}); + data.project.set('state', state); + let route = ProjectDetailRoute.create({userStore: data.store, intl}); + + try { + await route.model({project_id: '1a21', editing: false}).then( + () => assert.ok(false, `${state} ${task} 403 must reject`), + (error) => { + assert.strictEqual(error.status, 404, 'the existing denied-resource error is preserved'); + assert.strictEqual(error.messageKey, 'viewEditProject.error.relatedUnavailable'); + } + ); + assert.ok(data.relatedCalls.includes(task === 'networks' ? 'network' : 'stack'), 'the applicable request is still sent'); + assert.strictEqual(data.project.get('projectMembers'), data.members, 'global members were loaded first'); + } finally { + run(() => route.destroy()); + } + } + } +}); + test('a denied project cannot start unrelated network or policy-manager reads', async function(assert) { let data = fixture('project', {status: 404, message: 'Environment unavailable'}); let route = ProjectDetailRoute.create({userStore: data.store, intl: EmberObject.create({ @@ -89,6 +169,8 @@ test('loads project members through the supported link contract before cloning f assert.notStrictEqual(model.get('project'), data.project, 'editing uses a clone'); assert.strictEqual(model.get('project.projectMembers'), data.members, 'the imported members reach the editable clone'); assert.strictEqual(model.get('policyManager'), data.policyManager, 'the policy manager is loaded'); + assert.false(model.get('networkUnavailableForInactiveProject'), 'an active environment has no inactive notice'); + assert.deepEqual(data.relatedCalls, ['network', 'stack'], 'both active-environment reads still run'); assert.strictEqual(data.store.get('networkOptions.filter.accountId'), '1a21', 'the network lookup stays in the selected project'); assert.strictEqual(data.store.get('networkOptions.headers.X-Api-Project-Id'), '1a21', 'the network lookup uses its project policy'); assert.strictEqual(data.store.get('policyManagerOptions.headers.X-Api-Project-Id'), '1a21', 'policy manager lookup is scoped to the project'); diff --git a/translations/de-de.yaml b/translations/de-de.yaml index b13b473c46..11284ed9ee 100644 --- a/translations/de-de.yaml +++ b/translations/de-de.yaml @@ -3840,6 +3840,7 @@ viewEditProject: noMembers: 'Fügen Sie ein oder mehrere Mitglieder hinzu, die diese Umgebung nutzen können.' networkPolicy: label: Netzwerkrichtlinie + inactive: Diese Umgebung ist deaktiviert; die Netzwerkeinstellungen sind vorübergehend nicht verfügbar. Aktivieren Sie die Umgebung, um sie anzuzeigen oder zu bearbeiten. description: 'Steuern Sie, welche Container über das verwaltete Netzwerk miteinander kommunizieren dürfen.' unsupported: >- Diese Umgebung verfügt über Netzwerkrichtlinienregeln, die von UI noch nicht unterstützt werden. Bitte verwenden diff --git a/translations/en-us.yaml b/translations/en-us.yaml index 0302b7e84e..f5ef42be6b 100644 --- a/translations/en-us.yaml +++ b/translations/en-us.yaml @@ -4213,6 +4213,7 @@ viewEditProject: noMembers: Add one or more members who can use this environment. networkPolicy: label: Network Policy + inactive: This environment is deactivated; network settings are temporarily unavailable. Activate the environment to view or edit them. description: Control what containers are allowed to communicate with each other over the Managed Network. unsupported: This environment has network policy rules which are not yet supported by the UI. Please use the API to manage the policy. noManager: Network Policies are available only for environments using the PastureStack orchestration engine with the Network Policy Manager infrastructure template deployed. diff --git a/translations/fa-ir.yaml b/translations/fa-ir.yaml index 7649462fd2..c8092def89 100644 --- a/translations/fa-ir.yaml +++ b/translations/fa-ir.yaml @@ -3729,6 +3729,7 @@ viewEditProject: noMembers: افزودن اعضایی که می توانند از این محیط استفاده کنند. networkPolicy: label: خط مشی شبکه + inactive: این محیط غیرفعال است؛ تنظیمات شبکه موقتاً در دسترس نیستند. برای مشاهده یا ویرایش آن‌ها، محیط را فعال کنید. description: کنترل کنید که چه کانتینرهایی مجاز به برقراری ارتباط با یکدیگر از طریق شبکه مدیریت شده هستند. unsupported: >- این محیط دارای قوانین خط مشی شبکه است که هنوز توسط UI پشتیبانی نمی شود. لطفاً از API برای مدیریت خط مشی استفاده diff --git a/translations/fil-ph.yaml b/translations/fil-ph.yaml index 81daf2abf4..d08aace7d6 100644 --- a/translations/fil-ph.yaml +++ b/translations/fil-ph.yaml @@ -3786,6 +3786,7 @@ viewEditProject: noMembers: Magdagdag ng isa o higit pang miyembro na maaaring gumamit ng environment na ito. networkPolicy: label: Patakaran sa Network + inactive: Naka-deactivate ang environment na ito; pansamantalang hindi available ang mga setting ng network. I-activate ang environment upang makita o i-edit ang mga ito. description: Kontrolin kung anong mga container ang pinapayagang makipag-ugnayan sa isa't isa sa Pamamahala ng Network. unsupported: >- Ang kapaligirang ito ay may mga panuntunan sa patakaran sa network na hindi pa sinusuportahan ng UI. Pakigamit diff --git a/translations/fr-fr.yaml b/translations/fr-fr.yaml index 4d0145d346..623e9a6e8e 100644 --- a/translations/fr-fr.yaml +++ b/translations/fr-fr.yaml @@ -3785,6 +3785,7 @@ viewEditProject: noMembers: Ajouter un ou plusieurs membres qui peuvent utiliser cet environnement. networkPolicy: label: Politique de réseau + inactive: Cet environnement est désactivé ; les paramètres réseau sont temporairement indisponibles. Activez l’environnement pour les consulter ou les modifier. description: Contrôlez quels conteneurs sont autorisés à communiquer entre eux sur le réseau géré. unsupported: >- Cet environnement possède des règles de politique réseau qui ne sont pas encore prises en charge par UI. Veuillez diff --git a/translations/hu-hu.yaml b/translations/hu-hu.yaml index 0eaebccfea..f027a8aff1 100644 --- a/translations/hu-hu.yaml +++ b/translations/hu-hu.yaml @@ -3803,6 +3803,7 @@ viewEditProject: noMembers: Egy vagy több tag hozzáadása ehhez a környezethez. networkPolicy: label: Hálózati házirend + inactive: Ez a környezet inaktív; a hálózati beállítások átmenetileg nem érhetők el. A megtekintésükhöz vagy módosításukhoz aktiválja a környezetet. description: 'Szabályozhatod, hogy mely konténerek kommunikálhatnak egymással a kezelt hálózaton keresztül.' unsupported: >- Ez a környezet hálózati házirend-szabályokkal rendelkezik, amelyeket a UI még nem támogat. Kérjük, használja a diff --git a/translations/ja-jp.yaml b/translations/ja-jp.yaml index c3249d29c2..b354e8d0ed 100644 --- a/translations/ja-jp.yaml +++ b/translations/ja-jp.yaml @@ -3991,6 +3991,7 @@ viewEditProject: noMembers: この環境を利用することができる 1 つまたは複数のメンバーを追加します。 networkPolicy: label: ネットワークポリシー + inactive: この環境は無効です。ネットワーク設定は一時的に利用できません。表示または編集するには環境を有効にしてください。 description: 管理ネットワーク経由での相互通信を許可するコンテナをコントロールします。 unsupported: この環境には UI ではサポートされていない ネットワークポリシー ルール が存在します。ポリシーを管理するには API を利用してください。 noManager: ネットワークポリシーは Network Policy Manager テンプレートがデプロイされており PastureStack オーケストレーションエンジンを利用している環境下でのみ利用できます。 diff --git a/translations/ko-kr.yaml b/translations/ko-kr.yaml index 999b2efd3d..4f4b85f773 100644 --- a/translations/ko-kr.yaml +++ b/translations/ko-kr.yaml @@ -3643,6 +3643,7 @@ viewEditProject: noMembers: 이 환경을 사용할 수 있는 구성원을 한 명 이상 추가하세요. networkPolicy: label: 네트워크 정책 + inactive: 이 환경은 비활성 상태이며 네트워크 설정을 일시적으로 사용할 수 없습니다. 설정을 보거나 수정하려면 환경을 활성화하세요. description: 관리형 네트워크를 통해 서로 통신할 수 있는 컨테이너를 제어합니다. unsupported: 이 환경에는 UI에서 아직 지원되지 않는 네트워크 정책 규칙이 있습니다. 정책을 관리하려면 API를 이용하세요. noManager: 네트워크 정책은 네트워크 정책 관리자 인프라 템플릿이 배포된 PastureStack 오케스트레이션 엔진을 사용하는 환경에서만 사용할 수 있습니다. diff --git a/translations/pt-br.yaml b/translations/pt-br.yaml index c8f97e87db..0edfbf4bb4 100644 --- a/translations/pt-br.yaml +++ b/translations/pt-br.yaml @@ -3789,6 +3789,7 @@ viewEditProject: noMembers: Adicione um ou mais membros que possam usar este ambiente. networkPolicy: label: Política de Rede + inactive: Este ambiente está desativado; as configurações de rede estão temporariamente indisponíveis. Ative o ambiente para visualizá-las ou editá-las. description: Controle quais contêineres podem se comunicar entre si pela Rede Gerenciada. unsupported: >- Este ambiente possui regras de política de rede que ainda não são suportadas pelo UI. Use o API para gerenciar a diff --git a/translations/ru-ru.yaml b/translations/ru-ru.yaml index 14d5d81aa1..6656cba54c 100644 --- a/translations/ru-ru.yaml +++ b/translations/ru-ru.yaml @@ -3811,6 +3811,7 @@ viewEditProject: noMembers: 'Добавьте одиного или несколько участников, которые могут использовать эту среду.' networkPolicy: label: Сетевая политика + inactive: Эта среда отключена; настройки сети временно недоступны. Активируйте среду, чтобы просмотреть или изменить их. description: 'Контролируйте, каким контейнерам разрешено взаимодействовать друг с другом через управляемую сеть.' unsupported: >- В этой среде есть правила сетевой политики, которые еще не поддерживаются UI. Пожалуйста, используйте API для diff --git a/translations/uk-ua.yaml b/translations/uk-ua.yaml index cad66e5dfe..3d6dc9c7c3 100644 --- a/translations/uk-ua.yaml +++ b/translations/uk-ua.yaml @@ -3848,6 +3848,7 @@ viewEditProject: noMembers: 'Додайте одного або декілька учасників, які можуть використовувати це середовище.' networkPolicy: label: Політика мережі + inactive: Це середовище вимкнено; налаштування мережі тимчасово недоступні. Активуйте середовище, щоб переглянути або змінити їх. description: 'Ви можете контролювати, контейнери які можуть зв''язуватися один з одним через керовану мережу.' unsupported: >- Це середовище має правила мережевої політики, які ще не підтримуються UI. Для керування політикою використовуйте diff --git a/translations/zh-hans.yaml b/translations/zh-hans.yaml index 99b8f64242..70d077f736 100644 --- a/translations/zh-hans.yaml +++ b/translations/zh-hans.yaml @@ -3686,6 +3686,7 @@ viewEditProject: noMembers: 添加一个或者多个成员能够使用此环境的成员 networkPolicy: label: 网络策略 + inactive: 此环境已停用;网络设置暂不可用。启用环境后可查看或修改。 description: 控制受管网络中哪些容器彼此之间能够进行通讯 unsupported: 此环境中的网络策略设置暂不支持通过UI进行设置,请使用API管理网络策略。 noManager: 网络策略功能仅在使用 PastureStack 编排引擎且部署了Network Policy Manager基础设施模板的环境中可用。 diff --git a/translations/zh-tw.yaml b/translations/zh-tw.yaml index 0666aedc19..bdd975ab5d 100644 --- a/translations/zh-tw.yaml +++ b/translations/zh-tw.yaml @@ -4020,6 +4020,7 @@ viewEditProject: noMembers: 新增一個或多個成員能夠使用此環境的成員 networkPolicy: label: 網路策略 + inactive: 環境已停用;網路設定暫不可用。啟用後可檢視/修改。 description: 控制受管網路中哪些容器彼此之間能夠進行通訊 unsupported: 此環境中的網路策略設定暫不支援透過 UI 進行設定,請使用 API 管理網路策略。 noManager: 網路策略功能僅適用於使用 PastureStack 容器編排服務,且已部署網路策略管理基礎架構範本的環境。