Remove community workspace dropdown UI
Deploy / deploy (push) Successful in 2m30s
CI / Rust Checks (push) Successful in 5m32s
CI / UI Checks (push) Successful in 5s
CI / Deployment Manifests (push) Successful in 2s
CI / Frontend E2E (push) Failing after 4m46s

This commit is contained in:
github-ops
2026-06-19 17:59:57 +00:00
parent 58cfd86d90
commit 0d8a36e4d4
12 changed files with 52 additions and 337 deletions
-101
View File
@@ -929,81 +929,6 @@
max-width: 140px;
}
.ws-dropdown {
position: absolute;
top: calc(100% + 6px);
left: 0;
min-width: 230px;
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: 10px;
box-shadow: 0 8px 28px rgba(0,0,0,0.45);
z-index: 200;
overflow: hidden;
}
.ws-dropdown-label {
padding: 10px 14px 4px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
}
.ws-dropdown-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 14px;
cursor: pointer;
transition: background 0.1s;
}
.ws-dropdown-item:hover { background: rgba(255,255,255,0.04); }
.ws-dropdown-item.active { background: rgba(56,139,253,0.08); }
.ws-item-dot {
width: 30px;
height: 30px;
border-radius: 7px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 700;
color: #fff;
flex-shrink: 0;
}
.ws-item-info { flex: 1; min-width: 0; }
.ws-item-name {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ws-item-role { font-size: 11px; color: var(--text-muted); }
.ws-dropdown-footer { border-top: 1px solid var(--border-subtle); padding: 6px 8px; }
.ws-dropdown-create {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 6px 8px;
background: none;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
color: var(--text-secondary);
transition: background 0.1s, color 0.1s;
}
.ws-dropdown-create:hover { background: rgba(255,255,255,0.04); color: var(--text-primary); }
/* ═══════════════════════════════════════════════════
Agents page — info callout
═══════════════════════════════════════════════════ */
@@ -1554,32 +1479,6 @@
line-height: 1.5;
}
/* ── Workspace dropdown management links ── */
.ws-dropdown-divider {
height: 1px;
background: var(--border-subtle);
margin: 4px 0;
}
.ws-dropdown-mgmt-link {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 14px;
font-size: 13px;
color: var(--text-secondary);
text-decoration: none;
transition: background 0.1s, color 0.1s;
cursor: pointer;
}
.ws-dropdown-mgmt-link:hover {
background: rgba(255,255,255,0.04);
color: var(--text-primary);
}
.ws-dropdown-mgmt-link svg {
flex-shrink: 0;
opacity: 0.7;
}
@media (max-width: 900px) {
.toast-stack {
top: 72px;
+2 -7
View File
@@ -22,16 +22,11 @@
<span class="nav-logo-text">Crank</span>
</a>
<!-- Workspace switcher -->
<!-- Single workspace indicator -->
<div class="ws-switcher" id="ws-switcher">
<button class="ws-switcher-trigger" onclick="toggleWsSwitcher(event)">
<div class="ws-switcher-trigger">
<div class="ws-dot" id="ws-dot">A</div>
<span class="ws-current-name" id="ws-current-name">acme-workspace</span>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
</button>
<div class="ws-dropdown" id="ws-dropdown" hidden>
<div class="ws-dropdown-label" data-i18n="nav.workspaces">Your workspaces</div>
<div id="ws-dropdown-list"></div>
</div>
</div>
+2 -7
View File
@@ -39,16 +39,11 @@
<span class="nav-logo-text">Crank</span>
</a>
<!-- Workspace switcher -->
<!-- Single workspace indicator -->
<div class="ws-switcher" id="ws-switcher">
<button class="ws-switcher-trigger" onclick="toggleWsSwitcher(event)">
<div class="ws-switcher-trigger">
<div class="ws-dot" id="ws-dot">A</div>
<span class="ws-current-name" id="ws-current-name">acme-workspace</span>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
</button>
<div class="ws-dropdown" id="ws-dropdown" hidden>
<div class="ws-dropdown-label" data-i18n="nav.workspaces">Your workspaces</div>
<div id="ws-dropdown-list"></div>
</div>
</div>
+2 -7
View File
@@ -20,16 +20,11 @@
<span class="nav-logo-text">Crank</span>
</a>
<!-- Workspace switcher -->
<!-- Single workspace indicator -->
<div class="ws-switcher" id="ws-switcher">
<button class="ws-switcher-trigger" onclick="toggleWsSwitcher(event)">
<div class="ws-switcher-trigger">
<div class="ws-dot" id="ws-dot">A</div>
<span class="ws-current-name" id="ws-current-name">acme-workspace</span>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
</button>
<div class="ws-dropdown" id="ws-dropdown" hidden>
<div class="ws-dropdown-label" data-i18n="nav.workspaces">Your workspaces</div>
<div id="ws-dropdown-list"></div>
</div>
</div>
+2 -6
View File
@@ -54,15 +54,11 @@
<span class="nav-logo-text">Crank</span>
</a>
<!-- Single workspace indicator -->
<div class="ws-switcher" id="ws-switcher">
<button class="ws-switcher-trigger" onclick="toggleWsSwitcher(event)">
<div class="ws-switcher-trigger">
<div class="ws-dot" id="ws-dot">A</div>
<span class="ws-current-name" id="ws-current-name" data-testid="shell-workspace-name">workspace</span>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
</button>
<div class="ws-dropdown" id="ws-dropdown" hidden>
<div class="ws-dropdown-label" data-i18n="nav.workspaces">Your workspaces</div>
<div id="ws-dropdown-list"></div>
</div>
</div>
+2 -7
View File
@@ -20,16 +20,11 @@
<span class="nav-logo-text">Crank</span>
</a>
<!-- Workspace switcher -->
<!-- Single workspace indicator -->
<div class="ws-switcher" id="ws-switcher">
<button class="ws-switcher-trigger" onclick="toggleWsSwitcher(event)">
<div class="ws-switcher-trigger">
<div class="ws-dot" id="ws-dot">A</div>
<span class="ws-current-name" id="ws-current-name">acme-workspace</span>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
</button>
<div class="ws-dropdown" id="ws-dropdown" hidden>
<div class="ws-dropdown-label" data-i18n="nav.workspaces">Your workspaces</div>
<div id="ws-dropdown-list"></div>
</div>
</div>
+2 -7
View File
@@ -27,16 +27,11 @@
<span class="nav-logo-text">Crank</span>
</a>
<!-- Workspace switcher -->
<!-- Single workspace indicator -->
<div class="ws-switcher" id="ws-switcher">
<button class="ws-switcher-trigger" onclick="toggleWsSwitcher(event)">
<div class="ws-switcher-trigger">
<div class="ws-dot" id="ws-dot">A</div>
<span class="ws-current-name" id="ws-current-name">acme-workspace</span>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
</button>
<div class="ws-dropdown" id="ws-dropdown" hidden>
<div class="ws-dropdown-label" data-i18n="nav.workspaces">Your workspaces</div>
<div id="ws-dropdown-list"></div>
</div>
</div>
+2 -7
View File
@@ -20,16 +20,11 @@
<span class="nav-logo-text">Crank</span>
</a>
<!-- Workspace switcher -->
<!-- Single workspace indicator -->
<div class="ws-switcher" id="ws-switcher">
<button class="ws-switcher-trigger" onclick="toggleWsSwitcher(event)">
<div class="ws-switcher-trigger">
<div class="ws-dot" id="ws-dot">A</div>
<span class="ws-current-name" id="ws-current-name" data-testid="shell-workspace-name">acme-workspace</span>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
</button>
<div class="ws-dropdown" id="ws-dropdown" hidden>
<div class="ws-dropdown-label" data-i18n="nav.workspaces">Your workspaces</div>
<div id="ws-dropdown-list"></div>
</div>
</div>
+2 -7
View File
@@ -20,16 +20,11 @@
<span class="nav-logo-text">Crank</span>
</a>
<!-- Workspace switcher -->
<!-- Single workspace indicator -->
<div class="ws-switcher" id="ws-switcher">
<button class="ws-switcher-trigger" onclick="toggleWsSwitcher(event)">
<div class="ws-switcher-trigger">
<div class="ws-dot" id="ws-dot">A</div>
<span class="ws-current-name" id="ws-current-name">acme-workspace</span>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
</button>
<div class="ws-dropdown" id="ws-dropdown" hidden>
<div class="ws-dropdown-label" data-i18n="nav.workspaces">Your workspaces</div>
<div id="ws-dropdown-list"></div>
</div>
</div>
-4
View File
@@ -15,7 +15,6 @@ var TRANSLATIONS = {
'nav.settings': 'Settings',
'nav.logout': 'Log out',
'nav.workspaces': 'Your workspaces',
'nav.create_workspace': 'Create workspace',
'nav.notifications': 'Notifications',
'nav.menu': 'Menu',
'nav.account': 'Account',
@@ -469,7 +468,6 @@ var TRANSLATIONS = {
'workspace_setup.slug_hint': 'Only lowercase letters, numbers, and hyphens. Used in MCP endpoint URLs.',
'workspace_setup.description': 'Description',
'workspace_setup.description_placeholder': 'What does this workspace do?',
'workspace_setup.actions.create': 'Create workspace',
'workspace_setup.actions.save': 'Save changes',
'workspace_setup.create.title': 'Create a new workspace',
'workspace_setup.create.subtitle': 'This Community installation uses one workspace for MCP operations and agents.',
@@ -925,7 +923,6 @@ var TRANSLATIONS = {
'nav.settings': 'Настройки',
'nav.logout': 'Выйти',
'nav.workspaces': 'Ваши воркспейсы',
'nav.create_workspace': 'Создать воркспейс',
'nav.notifications': 'Уведомления',
'nav.menu': 'Меню',
'nav.account': 'Аккаунт',
@@ -1383,7 +1380,6 @@ var TRANSLATIONS = {
'workspace_setup.slug_hint': 'Только строчные буквы, цифры и дефисы. Используется в URL MCP endpoint-ов.',
'workspace_setup.description': 'Описание',
'workspace_setup.description_placeholder': 'Чем занимается этот воркспейс?',
'workspace_setup.actions.create': 'Создать воркспейс',
'workspace_setup.actions.save': 'Сохранить изменения',
'workspace_setup.create.title': 'Создать новый воркспейс',
'workspace_setup.create.subtitle': 'В Community используется один воркспейс для MCP-операций и агентов.',
+34 -173
View File
@@ -5,22 +5,10 @@ var WS_LIST = [
var workspaceLoadPromise = null;
var workspaceLoadFailed = false;
var lastWorkspaceId = null;
function tKey(key) {
return typeof t === 'function' ? t(key) : key;
}
function tfKey(key, vars) {
return typeof tf === 'function' ? tf(key, vars) : key;
}
function buildIconSvg(href, width, height) {
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', String(width));
svg.setAttribute('height', String(height));
var use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
use.setAttribute('href', href);
svg.appendChild(use);
return svg;
}
function workspaceColor(index) {
return ['#0d9488', '#7c3aed', '#0891b2', '#f59e0b', '#2563eb'][index % 5];
@@ -45,30 +33,6 @@ function mapWorkspace(record, index) {
};
}
function cachedWorkspaceId() {
try {
return localStorage.getItem('crank_workspace_id');
} catch (_error) {
return null;
}
}
function cachedWorkspaceSlug() {
try {
return localStorage.getItem('crank_workspace_slug');
} catch (_error) {
return null;
}
}
function sessionWorkspaceId() {
if (!window.CrankAuth || typeof window.CrankAuth.getCachedSession !== 'function') {
return null;
}
var session = window.CrankAuth.getCachedSession();
return session && session.current_workspace_id ? session.current_workspace_id : null;
}
function cacheCurrentWorkspace(workspace) {
if (!workspace) return;
try {
@@ -78,30 +42,7 @@ function cacheCurrentWorkspace(workspace) {
}
function resolveCurrentWorkspace() {
var workspaceId = sessionWorkspaceId() || cachedWorkspaceId();
var workspaceSlug = cachedWorkspaceSlug();
return WS_LIST.find(function(item) { return item.id === workspaceId; })
|| WS_LIST.find(function(item) { return item.slug === workspaceSlug; })
|| WS_LIST[0]
|| null;
}
function emitWorkspaceChange(workspace) {
if (!workspace) {
return;
}
lastWorkspaceId = workspace.id;
window.dispatchEvent(new CustomEvent('crank:workspacechange', { detail: workspace }));
}
function getCurrentWs() {
var workspace = resolveCurrentWorkspace();
if (workspace) {
cacheCurrentWorkspace(workspace);
}
return workspace;
return WS_LIST[0] || null;
}
function updateWorkspaceHeader(workspace) {
@@ -114,77 +55,24 @@ function updateWorkspaceHeader(workspace) {
}
}
function renderWorkspaceList() {
var current = getCurrentWs();
updateWorkspaceHeader(current);
function emitWorkspaceChange(workspace) {
if (!workspace || workspace.id === lastWorkspaceId) {
return;
}
lastWorkspaceId = workspace.id;
window.dispatchEvent(new CustomEvent('crank:workspacechange', { detail: workspace }));
}
document.querySelectorAll('.ws-dropdown-footer').forEach(function(footer) {
footer.hidden = true;
});
function getCurrentWs() {
var workspace = resolveCurrentWorkspace();
if (workspace) {
cacheCurrentWorkspace(workspace);
}
return workspace;
}
var list = document.getElementById('ws-dropdown-list');
if (!list) return;
window.CrankDom.clear(list);
WS_LIST.forEach(function(workspace) {
var active = current && workspace.id === current.id;
var item = document.createElement('div');
item.className = 'ws-dropdown-item' + (active ? ' active' : '');
item.addEventListener('click', function() {
var dd = document.getElementById('ws-dropdown');
if (dd) dd.hidden = true;
});
var dot = document.createElement('div');
dot.className = 'ws-item-dot';
dot.style.background = workspace.color;
dot.textContent = workspace.letter;
item.appendChild(dot);
var info = document.createElement('div');
info.className = 'ws-item-info';
var name = document.createElement('div');
name.className = 'ws-item-name';
name.textContent = workspace.name;
var role = document.createElement('div');
role.className = 'ws-item-role';
role.textContent = workspace.role;
info.appendChild(name);
info.appendChild(role);
item.appendChild(info);
if (active) {
var check = buildIconSvg(
(window.APP_BASE || '') + 'icons/general/check.svg#icon',
12,
12
);
item.appendChild(check);
}
list.appendChild(item);
});
var divider = document.createElement('div');
divider.className = 'ws-dropdown-divider';
list.appendChild(divider);
var manageLink = document.createElement('a');
manageLink.className = 'ws-dropdown-mgmt-link';
manageLink.href = (window.CrankRoutes && window.CrankRoutes.workspaceSetup) || '/workspace-setup';
manageLink.addEventListener('click', function() {
var dd = document.getElementById('ws-dropdown');
if (dd) dd.hidden = true;
});
manageLink.appendChild(
buildIconSvg(
(window.APP_BASE || '') + 'icons/general/settings.svg#icon',
14,
14
)
);
manageLink.appendChild(document.createTextNode(tKey('settings.ws.title')));
list.appendChild(manageLink);
function renderWorkspaceHeader() {
updateWorkspaceHeader(getCurrentWs());
}
async function loadWorkspaces() {
@@ -192,7 +80,7 @@ async function loadWorkspaces() {
workspaceLoadPromise = (async function() {
if (!window.CrankApi) {
renderWorkspaceList();
renderWorkspaceHeader();
return WS_LIST;
}
@@ -200,14 +88,15 @@ async function loadWorkspaces() {
var response = await window.CrankApi.listWorkspaces();
var items = (response && response.items ? response.items : []).map(mapWorkspace);
if (items.length > 0) {
WS_LIST = items;
WS_LIST = [items[0]];
}
workspaceLoadFailed = false;
} catch (_error) {
workspaceLoadFailed = true;
}
renderWorkspaceList();
renderWorkspaceHeader();
emitWorkspaceChange(getCurrentWs());
return WS_LIST;
}());
@@ -219,35 +108,14 @@ async function refreshWorkspaces() {
return loadWorkspaces();
}
function initWorkspaceSwitcher() {
renderWorkspaceList();
loadWorkspaces();
}
function toggleWsSwitcher(e) {
e.stopPropagation();
var dd = document.getElementById('ws-dropdown');
if (dd) dd.hidden = !dd.hidden;
}
function switchWorkspace(workspaceId) {
var workspace = WS_LIST.find(function(item) { return item.id === workspaceId; });
if (!workspace) return;
var dd = document.getElementById('ws-dropdown');
if (dd) dd.hidden = true;
cacheCurrentWorkspace(workspace);
renderWorkspaceList();
emitWorkspaceChange(workspace);
return Promise.resolve(workspace);
}
function setCurrentWorkspace(workspace) {
if (!workspace) {
return Promise.resolve(null);
if (workspace) {
WS_LIST = [workspace];
cacheCurrentWorkspace(workspace);
renderWorkspaceHeader();
emitWorkspaceChange(workspace);
}
return Promise.resolve(switchWorkspace(workspace.id));
return Promise.resolve(workspace || null);
}
window.getCurrentWorkspace = getCurrentWs;
@@ -256,19 +124,12 @@ window.refreshWorkspaces = refreshWorkspaces;
window.setCurrentWorkspace = setCurrentWorkspace;
window.hasWorkspaceApiFailure = function() { return workspaceLoadFailed; };
document.addEventListener('DOMContentLoaded', initWorkspaceSwitcher);
document.addEventListener('DOMContentLoaded', function() {
renderWorkspaceHeader();
loadWorkspaces();
});
window.addEventListener('crank:sessionchange', function() {
var workspace = getCurrentWs();
renderWorkspaceList();
if (workspace && workspace.id !== lastWorkspaceId) {
emitWorkspaceChange(workspace);
}
});
document.addEventListener('click', function(e) {
if (!e.target.closest('#ws-switcher')) {
var dd = document.getElementById('ws-dropdown');
if (dd) dd.hidden = true;
}
renderWorkspaceHeader();
emitWorkspaceChange(getCurrentWs());
});
+2 -4
View File
@@ -4,10 +4,8 @@ const { login, localized } = require('./helpers');
test('operations page shows demo catalog and filter works', async ({ page }) => {
await login(page);
await expect(page.locator('.page-heading')).toHaveText(localized('Operations', 'Операции'));
await page.locator('.ws-switcher-trigger').click();
await expect(page.locator('#ws-dropdown')).toBeVisible();
await page.mouse.click(900, 220);
await expect(page.locator('#ws-dropdown')).toBeHidden();
await expect(page.locator('.ws-switcher-trigger')).toBeVisible();
await expect(page.locator('#ws-dropdown')).toHaveCount(0);
await expect(page.locator('tbody tr')).toHaveCount(2);
await page.getByPlaceholder(localized('Search operations', 'Поиск операций')).fill('crm');
await expect(page.locator('tbody tr')).toHaveCount(1);