From 704f61cfccb30b67d6c8de5443b46fee8d8a5770 Mon Sep 17 00:00:00 2001 From: "a.tolmachev" Date: Sun, 3 May 2026 19:37:55 +0000 Subject: [PATCH] ui: gate settings workspace capabilities --- TASKS.md | 3 +- apps/ui/html/settings.html | 5 +- apps/ui/js/i18n.js | 16 ++++++ apps/ui/js/settings.js | 54 +++++++++++++++++++- apps/ui/tests/e2e/workspace-settings.spec.js | 2 + 5 files changed, 76 insertions(+), 4 deletions(-) diff --git a/TASKS.md b/TASKS.md index 8caa6f9..eedb02a 100644 --- a/TASKS.md +++ b/TASKS.md @@ -52,8 +52,9 @@ Progress: - `Secrets` page now has a mobile card layout instead of relying only on desktop table presentation - `Usage` page now has a mobile card layout instead of relying only on desktop table presentation - `Agents` page and modal now explain separate agent endpoints, next-step machine access, and stable slug expectations + - `Settings` page now renders workspace protocol choices from build capabilities and uses honest Community notification copy instead of generic future promises - pending: - - remaining `Settings` and workspace commercial UX polish + - remaining `Settings` and workspace spacing/sticky polish ## Planned diff --git a/apps/ui/html/settings.html b/apps/ui/html/settings.html index ee36ee1..4ac3b5c 100644 --- a/apps/ui/html/settings.html +++ b/apps/ui/html/settings.html @@ -161,13 +161,14 @@ -
+
+
This build limits the default protocol to the capabilities available in the current edition.
@@ -308,7 +309,7 @@
- Available later. Notification routing and personal preferences will be added later. + Not included in this build. Notification routing and personal preferences are outside the current Community surface.
diff --git a/apps/ui/js/i18n.js b/apps/ui/js/i18n.js index b7b712e..688dd04 100644 --- a/apps/ui/js/i18n.js +++ b/apps/ui/js/i18n.js @@ -388,6 +388,8 @@ var TRANSLATIONS = { 'settings.page.title': 'Account settings', 'settings.page.subtitle': 'Manage your profile, password, workspace settings, and the capability limits of this build.', 'settings.ws.slug_hint': 'Used in API endpoints and across the console. Only lowercase letters, numbers and hyphens.', + 'settings.ws.protocol_hint': 'This build limits the default protocol to the capabilities available in the current edition.', + 'settings.ws.protocol_hint_capabilities': 'This build allows {protocols} as workspace defaults. Additional protocols depend on the edition.', 'settings.ws.description_optional': 'Description (optional)', 'settings.ws.description_placeholder': 'What does this workspace do?', 'settings.profile.avatar_hint': 'Your avatar is generated from your display name and email.', @@ -441,6 +443,12 @@ var TRANSLATIONS = { 'settings.notifications.title': 'Notifications', 'settings.notifications.not_ready_title': 'Not included in this build.', 'settings.notifications.not_ready_body': 'Notification routing and per-user preferences are outside the current Community surface.', + 'settings.notifications.capability_title_community': 'Community build boundary', + 'settings.notifications.capability_body_community': 'Per-user notification routing and delivery channels are outside the current Community surface. Use logs and usage views for operational follow-up in this build.', + 'settings.notifications.capability_title_enterprise': 'Enterprise delivery controls', + 'settings.notifications.capability_body_enterprise': 'Notification routing depends on enterprise delivery controls and is wired separately from the Community console surface.', + 'settings.notifications.capability_title_cloud': 'Cloud delivery controls', + 'settings.notifications.capability_body_cloud': 'Notification routing depends on hosted delivery controls and is wired separately from the Community console surface.', 'settings.planned_badge': 'Planned', 'settings.notifications.spike_title': 'Operation error spike', 'settings.notifications.spike_body': 'Alert when error rate exceeds a rolling threshold for a published tool.', @@ -1457,6 +1465,8 @@ var TRANSLATIONS = { 'settings.page.title': 'Настройки аккаунта', 'settings.page.subtitle': 'Управляйте профилем, паролем, настройками воркспейса и границами возможностей текущей сборки.', 'settings.ws.slug_hint': 'Используется в API endpoint-ах и по всей консоли. Только строчные буквы, цифры и дефисы.', + 'settings.ws.protocol_hint': 'Эта сборка ограничивает протокол по умолчанию возможностями текущей редакции.', + 'settings.ws.protocol_hint_capabilities': 'В этой сборке в качестве протокола воркспейса доступны {protocols}. Дополнительные протоколы зависят от редакции.', 'settings.ws.description_optional': 'Описание (необязательно)', 'settings.ws.description_placeholder': 'Чем занимается этот воркспейс?', 'settings.profile.avatar_hint': 'Аватар строится из отображаемого имени и email.', @@ -1510,6 +1520,12 @@ var TRANSLATIONS = { 'settings.notifications.title': 'Уведомления', 'settings.notifications.not_ready_title': 'Не входит в эту сборку.', 'settings.notifications.not_ready_body': 'Маршрутизация уведомлений и персональные настройки находятся вне текущей поверхности Community.', + 'settings.notifications.capability_title_community': 'Граница редакции Community', + 'settings.notifications.capability_body_community': 'Персональные каналы доставки и маршрутизация уведомлений не входят в текущую поверхность Community. В этой сборке для операционного контроля используйте журналы и представления использования.', + 'settings.notifications.capability_title_enterprise': 'Контур доставки Enterprise', + 'settings.notifications.capability_body_enterprise': 'Маршрутизация уведомлений зависит от корпоративного контура доставки и подключается отдельно от поверхности консоли Community.', + 'settings.notifications.capability_title_cloud': 'Контур доставки Cloud', + 'settings.notifications.capability_body_cloud': 'Маршрутизация уведомлений зависит от облачного контура доставки и подключается отдельно от поверхности консоли Community.', 'settings.planned_badge': 'Запланировано', 'settings.notifications.spike_title': 'Всплеск ошибок операции', 'settings.notifications.spike_body': 'Алерт, когда доля ошибок превышает скользящий порог для опубликованного инструмента.', diff --git a/apps/ui/js/settings.js b/apps/ui/js/settings.js index 7f23f4e..d01e8e6 100644 --- a/apps/ui/js/settings.js +++ b/apps/ui/js/settings.js @@ -75,6 +75,55 @@ function capabilityLabel(key) { return tKey('settings.capability.' + key); } +function renderWorkspaceProtocolOptions(capabilities, selectedProtocol) { + var select = document.getElementById('settings-ws-protocol'); + if (!select) { + return; + } + + var supportedProtocols = capabilities && Array.isArray(capabilities.supported_protocols) && capabilities.supported_protocols.length + ? capabilities.supported_protocols + : ['rest', 'graphql', 'grpc']; + var fallbackProtocol = supportedProtocols.indexOf('rest') !== -1 ? 'rest' : supportedProtocols[0]; + var effectiveSelection = supportedProtocols.indexOf(selectedProtocol) !== -1 + ? selectedProtocol + : fallbackProtocol; + + select.innerHTML = ''; + supportedProtocols.forEach(function (protocol) { + var option = document.createElement('option'); + option.value = protocol; + option.textContent = capabilityLabel(protocol); + option.selected = protocol === effectiveSelection; + select.appendChild(option); + }); +} + +function populateWorkspaceCapabilityHint(capabilities) { + var body = document.getElementById('settings-ws-protocol-hint'); + if (!body || !capabilities) { + return; + } + + var protocols = (capabilities.supported_protocols || []).map(capabilityLabel).join(', '); + body.textContent = tfKey('settings.ws.protocol_hint_capabilities', { + protocols: protocols || capabilityLabel('none'), + }); +} + +function populateNotificationsCapabilityHint(capabilities) { + var title = document.getElementById('settings-notifications-capability-title'); + var body = document.getElementById('settings-notifications-capability-body'); + if (!title || !body || !capabilities) { + return; + } + + title.textContent = tKey('settings.notifications.capability_title_' + capabilities.edition); + body.textContent = tfKey('settings.notifications.capability_body_' + capabilities.edition, { + edition: capabilities.edition, + }); +} + function populateCapabilities(capabilities) { var title = document.getElementById('settings-security-capability-title'); var body = document.getElementById('settings-security-capability-body'); @@ -96,6 +145,9 @@ function populateCapabilities(capabilities) { max_users: limits.max_users_per_workspace == null ? capabilityLabel('unlimited') : String(limits.max_users_per_workspace), max_agents: limits.max_agents_per_workspace == null ? capabilityLabel('unlimited') : String(limits.max_agents_per_workspace), }); + populateWorkspaceCapabilityHint(capabilities); + populateNotificationsCapabilityHint(capabilities); + renderWorkspaceProtocolOptions(capabilities, document.getElementById('settings-ws-protocol').value || 'rest'); } function populateProfile(session) { @@ -310,7 +362,7 @@ async function loadWorkspaceSettings() { document.getElementById('settings-ws-display-name').value = item.display_name || ''; document.getElementById('settings-ws-description').value = settings.description || ''; document.getElementById('settings-ws-timezone').value = settings.timezone || 'UTC'; - document.getElementById('settings-ws-protocol').value = settings.default_protocol || 'rest'; + renderWorkspaceProtocolOptions(settingsCapabilities, settings.default_protocol || 'rest'); document.querySelectorAll('#section-workspace .lang-btn').forEach(function (button) { button.classList.toggle('active', button.dataset.lang === (settings.language || (localStorage.getItem('crank_lang') || 'en'))); diff --git a/apps/ui/tests/e2e/workspace-settings.spec.js b/apps/ui/tests/e2e/workspace-settings.spec.js index 1e64604..e4d33ae 100644 --- a/apps/ui/tests/e2e/workspace-settings.spec.js +++ b/apps/ui/tests/e2e/workspace-settings.spec.js @@ -15,4 +15,6 @@ test('workspace and settings pages show live session data', async ({ page }) => await expect(page.locator('#section-security')).toBeVisible(); await expect(page.locator('#settings-security-capability-title')).toContainText(localized('Community build', 'Редакция Community')); await expect(page.locator('#settings-security-capability-body')).toContainText(localized('static agent keys', 'статические ключи агентов')); + await expect(page.locator('#settings-ws-protocol-hint')).toContainText(localized('REST / HTTP', 'REST / HTTP')); + await expect(page.locator('#settings-notifications-capability-title')).toContainText(localized('Community build boundary', 'Граница редакции Community')); });